8章 配列
https://gyazo.com/51ba590422cb6160a4960060509a9941
初めてのJavaScript第三版
前: 7章 スコープ
次: 9章 オブジェクト指向とオブジェクト指向プログラミング
8.1 配列の基本
各要素には0から始まる添字(インデックス)によってアクセスする
配列には異なる型の要素を入れることができる
配列リテラルは[...]で表現され、配列の要素にも[...]を使ってアクセスする
lengthというプロパティで配列の要素数を知ることができる
配列の最後の要素の添字よりも大きな添字を使って代入を行うと配列が自動的に大きくなる
値が指定されていない要素にはundefinedが暗黙のうちに代入される
配列の生成にはArrayコンストラクタを使うこともできる(あまり使われない)
8.2 配列要素の操作
破壊的なメソッドと新しい配列を返すメソッドに注意
あるメソッドが内容を変更するか新しい配列を返すかは覚えるしかない
例) pushは配列を変更する。concatは新しい配列を返す
8.2.1 先頭あるいは最後の要素に対する操作
先頭要素: arr[0]
最後の要素: arr[arr.length-1]
以下のメソッドは配列そのものを変更する
push: 最後に要素を追加。
pop: 最後の要素を削除。
shift: 先頭の要素を削除。
unshift: 先頭に要素を追加。
上の4つのメソッドはコンピュータサイエンスで使われる用語でスタックやキューと呼ばれるデータ構造に対して行われる操作
code:js
let arr = "b", "c", "d";
console.log(arr.push("e")); // 4 ←現在の長さ(要素数)
console.log(arr); // 'b', 'c', 'd', 'e'
console.log(arr.pop()); // e
console.log(arr); // 'b', 'c', 'd'
console.log(arr.unshift("a")); // 4 ←現在の長さ
console.log(arr); // 'a', 'b', 'c', 'd'
console.log(arr.shift()); // a
console.log(arr); // 'b', 'c', 'd'
8.2.2 複数要素の追加
concat: 複数の要素を配列に追加し、配列のコピーを戻す
concatに対して配列を渡すと配列をバラバラにし、もともとの配列の最後に追加してくれる
引数の配列の要素が配列の場合、内側の配列まではバラバラにしない
code:js
let arr = 1, 2, 3;
let arr2 = arr.concat(4, 5, 6);
console.log(arr); // 1, 2, 3 (← 変更なし。以降も同じ)
console.log(arr2); // 1, 2, 3, 4, 5, 6
arr2 = arr.concat(4, 5, 6); // (配列を渡す)
console.log(arr2); // 1, 2, 3, 4, 5, 6
arr2 = arr.concat(4, 5, 6);
console.log(arr2); // 1, 2, 3, 4, 5, 6
arr2 = arr.concat(4, 5, 6, 7); // 引数は2つでいずれも配列
console.log(arr2); // 1, 2, 3, 4, 5, 6, 7
arr2 = arr.concat([4, 5, 6]); // 引数は配列ひとつでその2番目の要素が配列
console.log(arr2); // [ 1, 2, 3, 4, 5, 6 ]
8.2.3 部分配列
slice: 2個の引数を渡し、第1引数に指定した場所から第2引数に指定した場所の前までの要素からなる部分配列を戻す
第1引数: 開始要素
第2引数: 終了要素。終了要素自体は部分配列に含まれない。省略すると配列の最後までになる。
引数に負の値を指定すると最後の要素から数えた場所を指定することになる
code:js
let arr = 11, 12, 13, 14, 15;
let arr2 = arr.slice(3); // arr3から後ろ
console.log(arr2); // 14, 15
console.log(arr); // 11, 12, 13, 14, 15 (変更なし。以降も同じ)
arr2 = arr.slice(2, 4); // arr2からarr4のひとつ前まで
console.log(arr2); // 13, 14
arr2 = arr.slice(-2); // 最後から2番目以降
console.log(arr2); // 14, 15
arr2 = arr.slice(1, -2); // arr1から、最後から2番目のひとつ前まで
console.log(arr2); // 12, 13
arr2 = arr.slice(-2, -1); // 最後から2番目から最後から1番目のひとつ前まで
console.log(arr2); // 14
8.2.4 途中の要素の削除や途中への要素の追加
splice: 配列の任意の場所を指定して内容を変更することができる
第1引数: 変更を開始する添字
第2引数: 削除する要素の数(0を指定すると要素を削除しない)
第3引数以降: 追加する要素
code:js
let arr = 1, 5, 7;
let arr2 = arr.splice(1, 0, 2, 3, 4); // arr1から2, 3, 4が追加される
console.log(arr); // 1, 2, 3, 4, 5, 7
console.log(arr2); // [] ←何も削除されていない
arr2 = arr.splice(5, 0, 6); // arr5に6が追加されて、以降ひとつずつ後ろへ
console.log(arr); // 1, 2, 3, 4, 5, 6, 7
console.log(arr2); // [] ←何も削除されていない
arr2 = arr.splice(1, 2) // arr1から2個削除
console.log(arr); // 1, 4, 5, 6, 7
console.log(arr2); // 2, 3 ←削除された要素
arr2 = arr.splice(2, 1, 'a', 'b'); // arr2から1個削除して'a'と'b'をそこに追加
console.log(arr); // 1, 4, 'a', 'b', 6, 7
console.log(arr2); // 5 ←削除された要素
8.2.5 配列内の要素の削除や置換
copyWithin: 配列からの一連の要素を指定し、それらを配列の他の部分にコピーし、そこに以前あった要素を上書きする。
ES2015から追加
破壊的変更
第1引数: どこにコピーするか(ターゲット)
第2引数: どこからコピーするか
第3引数(オプション): コピーを終了する場所
第2, 第3引数には負の値を指定することで配列の最後の要素から数える
code:js
let arr = 11, 12, 13, 14;
let arr2 = arr.copyWithin(1, 2); // arr1の位置から置き換える。arr2から最後までコピーする
console.log(arr); // 11, 13, 14, 14
console.log(arr2); // 11, 13, 14, 14 ← copyWithinはオブジェクト自身を返す
console.log(arr.copyWithin(2, 0, 2)); // 11, 13, 11, 13
// ↑ arr2の位置から置き換える。arr0からarr2の前までコピーする
console.log(arr.copyWithin(0, -3, -1)); // 13, 11, 11, 13
// ↑ arr0の位置から置き換える。最後から3番目の要素から最後の要素のひとつ前まで
// (つまりarr1からarr2まで)コピーする
console.log(arr2); // 13, 11, 11, 13
// ↑ arrを変更すると同じ配列を指しているarr2も変わる
8.2.6 配列を特定の値で埋める
fill: 複数の要素の値を一度に指定する
ES2015から追加
破壊的変更
Arrayコンストラクタと一緒に使えば複数の要素の初期値を指定することができる
配列の一部だけの値を指定することもできる
負の値は最後の要素から数える
code:js
let arr = new Array(5).fill(1); // 大きさ5の配列を作り全体を1で初期化する
console.log(arr); // 1, 1, 1, 1, 1
let arr2 = arr.fill("a"); // すべての要素に "a" を代入する
console.log(arr); // 'a', 'a', 'a', 'a', 'a'
console.log(arr2); // 'a', 'a', 'a', 'a', 'a' ← fillはオブジェクト自身を返す
console.log(arr.fill("b", 1)); // 'a', 'b', 'b', 'b', 'b'
// ↑ arr1から最後まで "b" を代入する
console.log(arr.fill("c", 2, 4)); // 'a', 'b', 'c', 'c', 'b'
// ↑ arr2からarr4の前まで(つまりarr3まで) "c" を代入する
console.log(arr.fill(5.5, -4)); // 'a', 5.5, 5.5, 5.5, 5.5
// ↑ 最後から4番目の要素(つまりarr1)から最後まで 5.5 を代入する
console.log(arr.fill(0, -3, -1)); // 'a', 5.5, 0, 0, 5.5
// ↑ 最後から3番目の要素(つまりarr2)から最後の要素のひとつ前(つまりarr3)まで0を代入
8.2.7 逆転とソート
reverseは配列の要素を逆順に並び替える
破壊的変更
sortは配列の要素をソートする
破壊的変更
ソート時に関数を指定することもできる
code:js
let arr = [{ name: "Suzanne" }, { name: "Jim" },
{ name: "Trevor" }, { name: "Amanda" }];
console.log(arr);
arr.sort((a, b) => a.name > b.name); // nameでソート
console.log("------");
console.log(arr);
arr.sort((a, b) => a.name1 < b.name1); // nameの2文字目で逆順にソート
console.log("------");
console.log(arr);
/* 実行結果
[ { name: 'Suzanne' },
{ name: 'Jim' },
{ name: 'Trevor' },
{ name: 'Amanda' } ]
------
[ { name: 'Amanda' },
{ name: 'Jim' },
{ name: 'Suzanne' },
{ name: 'Trevor' } ]
------
[ { name: 'Suzanne' },
{ name: 'Trevor' },
{ name: 'Amanda' },
{ name: 'Jim' } ]
*/
8.3 検索
indexOf: 指定した値に厳密に等しい(===)要素を持つ最初の添字を返す
lastIndexOf: 同様に最後の添字を返す
見つからなかった場合は-1を返す
配列の一部だけを検索したい場合は開始位置を指定する
findIndex: 比較の際に用いる関数を指定することができる
見つかった場合は添字を返す
見つからなかった場合は-1
検索開始位置の指定や最終要素から検索するメソッドはない
find: 添字ではなく要素自体を返す
比較に用いる関数を指定できる
見つからなかった場合はundefinedを返す
find、findIndexに指定する関数
現在の要素の添字と配列全体も第2、第3引数として渡すことができる
関数の呼び出し時に変数thisが何を指すかを指定することもできる
code:js
const arr = 1, 17, 16, 5, 4, 16, 10, 3, 49;
console.log(arr.find((x, i) => i > 2 && Number.isInteger(Math.sqrt(x)))); // 4
/* 添字(第2引数)が2より大きくて、ルートを取った値が整数になる */
console.log(arr.find((x, i) => i > 5 && Number.isInteger(Math.sqrt(x)))); // 49
code:js
class Person {
constructor(name) {
this.name = name;
this.id = Person.nextId++;
}
}
Person.nextId = 0;
const Ichiro = new Person("一郎"), /* id 0 */
Jiro = new Person("次郎"), /* id 1 */
Saburo = new Person("三郎"), /* id 2 */
Shiro = new Person("四郎"); /* id 3 */
const arr = Ichiro, Jiro, Saburo, Shiro;
/* 1. IDを使って直接比較 */
console.log(arr.find(p => p.id === Saburo.id)); // Person { name: '三郎', id: 2 }
/* 2. thisを利用。thisを定数「Saburo」に指定 */
console.log(arr.find(function(p) {return p.id === this.id}, Saburo));
// Person { name: '三郎', id: 2 }
/* アロー関数ではthisは「語彙的に(lexically)」に束縛される(6章参照) */
console.log(arr.find(p => p.id === this.id, Saburo)); // undefined
some: 指定の条件を満たす要素が見つかった場合trueを返す、それ以外はfalseを返す
code:js
const arr = 5, 7, 12, 15, 17;
console.log(arr.some(x => x%2===0)); // true (12は偶数)
console.log(arr.some(x => Number.isInteger(Math.sqrt(x)))); // false
every: 配列の全要素が指定の条件を満たすときにtrueを返し、そうでないときにfalseを返す
code:js
const arr = 4, 6, 16, 36;
console.log(arr.every(x => x%2===0)); // true (すべてが偶数)
console.log(arr.every(x => Number.isInteger(Math.sqrt(x)))); // false (6は整数の2乗ではない)
someもeveryも第2引数を使って呼び出されたときのthisの値を指定することができる
8.4 mapとfilter
map: 配列内の要素を変換する
新しい配列を戻す
code:js
const cart = { 名前: "iPhone", 価格: 54800}, { 名前: "Android", 価格: 49800};
const names = cart.map(x => x.名前); // 各オブジェクトの「名前」からなる配列を新たに作る
console.log(names); // 'iPhone', 'Android'
const prices = cart.map(x => x.価格);
console.log(prices); // 54800, 49800
const discountPrices = prices.map(x => x*0.8); // 2割引の価格
console.log(discountPrices); // 43840, 39840
const lcNames = names.map(x => x.toLowerCase()); // 小文字にする
// const lcNames = names.map(String.toLowerCase);
// ↑Firefoxではこれでも動くが、nodeやGoogle Chromeでは動作しない
console.log(lcNames); // 'iphone', 'android'
指定した関数が呼び出されるときにその関数に以下の3つが渡される
要素そのもの
その要素の添字
配列そのもの
code:js
const items = "iPhone", "Android";
const prices = 54800, 49800;
const cart = items.map((x, i) => ({ 名前: x, 価格: pricesi}));
console.log(cart);
// { '名前': 'iPhone', '価格': 54800 }, { '名前': 'Android', '価格': 49800 }
filter: 配列から扶養な要素を取り去る
条件にマッチしない要素が削除された新しい配列を戻す
code:js
const カードの束 = [];
for(let マーク of 'ハート', 'クローバー', 'ダイア', 'スペード') // 全カードを生成
for(let 数字=1; 数字<=13; 数字++)
カードの束.push({ マーク, 数字});
let 選択されたカード = カードの束.filter(カード => カード.数字 === 2);
console.log(選択されたカード); // 2のカードのみ。次の4枚
/* [ { 'マーク': 'ハート', '数字': 2 },
{ 'マーク': 'クローバー', '数字': 2 },
{ 'マーク': 'ダイア', '数字': 2 },
{ 'マーク': 'スペード', '数字': 2 } ] */
選択されたカード = カードの束.filter(カード => カード.マーク === 'ダイア');
console.log(選択されたカード); // ダイアのカードのみ 13枚(以下詳細は省略)
選択されたカード = カードの束.filter(カード => カード.数字 > 10);
console.log(選択されたカード); // 絵札のみ(3×4=12枚)
選択されたカード
= カードの束.filter(カード => カード.数字 > 10 && カード.マーク === 'ハート');
console.log(選択されたカード); // ハートの絵札のみ3枚
filterとmapの組み合わせ
code:js
function 記号表現に変換する(カード) {
const マーク名_絵文字
= { 'ハート': '♡', 'クローバー': '♧', 'ダイア': '♢', 'スペード': '♤' };
const 数字からAJQK = { 1: 'A', 11: 'J', 12: 'Q', 13: 'K' };
for(let i=2; i<=10; i++) 数字からAJQKi = i;
return マーク名_絵文字カード.マーク+数字からAJQKカード.数字;
}
const カードの束 = [];
for(let マーク of 'ハート', 'クローバー', 'ダイア', 'スペード') // 全カードを生成
for(let 数字=1; 数字<=13; 数字++)
カードの束.push({ マーク, 数字});
let 選択されたカード_記号表現
= カードの束.filter(カード => カード.数字 === 2).map(記号表現に変換する);
console.log(選択されたカード_記号表現); // '♡2', '♧2', '♢2', '♤2'
選択されたカード_記号表現
= カードの束.filter(カード => カード.マーク === 'ダイア').map(記号表現に変換する);
console.log(選択されたカード_記号表現);
// [ '♢A', '♢2', '♢3', '♢4', '♢5', '♢6', '♢7', '♢8',
// '♢9', '♢10', '♢J', '♢Q', '♢K' ]
選択されたカード_記号表現
= カードの束.filter(カード => カード.数字 > 10).map(記号表現に変換する);
console.log(選択されたカード_記号表現);
// [ '♡J', '♡Q', '♡K', '♧J', '♧Q', '♧K', '♢J', '♢Q',
// '♢K', '♤J', '♤Q', '♤K' ]
選択されたカード_記号表現
= カードの束.filter(カード => カード.数字 > 10 && カード.マーク === 'ハート')
.map(記号表現に変換する);
console.log(選択されたカード_記号表現); // '♡J', '♡Q', '♡K'
8.5 reduce
reduce: 配列全体を変換する
配列を一つの値に変換するのに使われる事が多い
関数を指定できる
accumulator: 最終的に配列が変換される先
現在の配列要素
現在の添字
配列そのもの
オプションで初期値を指定することができる
初期値がundefinedの場合は最初の要素を初期値として取り、2番目の要素から関数を呼び出し始める
したがって、以下のサンプルコードの初期値0はなくてもよい
code:js
const arr = 5, 7, 2, 4;
const sum = arr.reduce((a, x) => a += x, 0);
console.log(sum); // 18
const sum2 = arr.reduce((a, x) => a + x, 0); /* 「+=」の「=」 は省略できる */
console.log(sum2); // 18
オブジェクトをアキュミュレータとして使うこともできる
code:js
const words = ["Beachball", "Rodeo", "Angel",
"Aardvark", "Xylophone", "November", "Chocolate",
"Papaya", "Uniform", "Joker", "Clover", "Bali"];
const alphabetical = words.reduce((a, x) => {
if(!a[x0]) // 先頭文字のプロパティがあるかチェック
a[x0] = []; // なければ空で作成
a[x0].push(x); // 現在の要素を記憶
// console.log(a[x0]); // 途中経過を見るにはこれを有効に
return a; }, {}); // オブジェクトを返す
console.log(alphabetical);
/* 実行結果
{ B: 'Beachball', 'Bali' ,
R: 'Rodeo' ,
A: 'Angel', 'Aardvark' ,
X: 'Xylophone' ,
N: 'November' ,
C: 'Chocolate', 'Clover' ,
P: 'Papaya' ,
U: 'Uniform' ,
J: 'Joker' }
*/
// 別の例
const 単語リスト
= ["ビーチボール", "ルービックキューブ", "ブタペスト", "トロッコ",
"コンゴ", "ゴースト", "トーラス", "スマート", "トンガ",
"ガラパゴス", "ストリート", "トーマス", "ストレッチ",
"チベット", "トキ", "キツツキ", "キリン"];
const 先頭文字で分類したもの = 単語リスト.reduce((a, x) => {
if(!a[x0]) a[x0] = [];
a[x0].push(x);
return a; }, {});
console.log(先頭文字で分類したもの);
/* 実行結果
{ 'ビ': 'ビーチボール' ,
'ル': 'ルービックキューブ' ,
'ブ': 'ブタペスト' ,
'ト': 'トロッコ', 'トーラス', 'トンガ', 'トーマス', 'トキ' ,
'コ': 'コンゴ' ,
'ゴ': 'ゴースト' ,
'ス': 'スマート', 'ストリート', 'ストレッチ' ,
'ガ': 'ガラパゴス' ,
'チ': 'チベット' ,
'キ': 'キツツキ', 'キリン' }
*/
8.6 配列関連のメソッドと削除された要素、定義されていない要素
map、filter、reduceは削除されたり値が代入されたことのない要素に対しては関数を呼び出さない
8.7 join
join: 配列の各要素をまとめて一つの文字列を返す
第1引数はセパレータ
デフォルトは,
削除された要素やnullやundefinedは空文字列になる
8.8 まとめ
この章に登場した配列関連の関数はArray.prototypeのメソッド
#JavaScript